//
//  SLReactiveProgrammingDemoController.m
//  Objective-C
//
//  Created by 陆承东 on 2019/3/6.
//  Copyright © 2019 S_Line. All rights reserved.
//

#import "SLReactiveProgrammingDemoController.h"

#import "SLRACBlueView.h"

#import "ReactiveObjC.h"

@interface SLReactiveProgrammingDemoController ()

@property (nonatomic, strong) id<RACSubscriber> subscriber;
@property (nonatomic, strong) RACDisposable *signalDisposable;

@property (nonatomic, strong) RACSubject *subject;
@property (nonatomic, strong) RACReplaySubject *replaySubject;

@property (nonatomic, strong) id<RACSubscriber> connectSubscriber;
@property (nonatomic, strong) RACDisposable *connectSignalDisposable;

@property (nonatomic, strong) RACCommand *command;

@property (nonatomic, weak) UIButton *sendSignalButton;
@property (nonatomic, weak) UIButton *disposeSignalButton;
@property (nonatomic, weak) UILabel *textLabel;
@property (nonatomic, weak) UITextField *inputTextField;
@property (nonatomic, weak) SLRACBlueView *blueView;

@end

@implementation SLReactiveProgrammingDemoController

- (void)viewDidAppear:(BOOL)animated {
    [super viewDidAppear:animated];
    
    [self testRacSignal];
    [self testRacSubject];
    [self testRACReplaySubject];
    [self testRACSequenceAndRACTuple];
    [self testRACMulticastConnection];
    [self testRACCommand];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self addNotify];
    
    [self setupNavBar];
    
    [self setupView];
    
    [self setupLayout];
}

- (void)setupNavBar {
    self.navTitle = @"ReactiveProgramming";
}

- (void)addNotify {
    [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillChangeFrameNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - UIKeyboardWillChangeFrameNotification - x: %@", x);
    }];
}

- (void)setupView {
    UIButton *sendSignalButton = [[UIButton alloc] init];
    self.sendSignalButton = sendSignalButton;
    [sendSignalButton setTitle:@"发送信号" forState:UIControlStateNormal];
    [sendSignalButton setBackgroundImage:[UIImage imageWithColor:[UIColor colorWithHexString:@"F37021"]] forState:UIControlStateNormal];
//    [sendSignalButton addTarget:self action:@selector(sendSignalButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    @weakify(self);
    [[sendSignalButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        @strongify(self);
        NSLog(@"----------> SLReactiveProgrammingDemoController - sendSignalButton - touchupinside x: %@", x);
        
        // 3. 发送信号
        [self.subscriber sendNext:@"signal"];
        [self.subject sendNext:@"subject"];
        [self.replaySubject sendNext:@"replaySubject3"];
        [self.connectSubscriber sendNext:@"connectSignal"];
        [self.command execute:@"signal3"];
    }];
    [self.view addSubview:sendSignalButton];
    
    UIButton *disposeSignalButton = [[UIButton alloc] init];
    self.disposeSignalButton = disposeSignalButton;
    [disposeSignalButton setTitle:@"发送信号" forState:UIControlStateNormal];
    [disposeSignalButton setBackgroundImage:[UIImage imageWithColor:[UIColor colorWithHexString:@"F37021"]] forState:UIControlStateNormal];
//    [disposeSignalButton addTarget:self action:@selector(disposeSignalButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [[disposeSignalButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
        @strongify(self);
        NSLog(@"----------> SLReactiveProgrammingDemoController - disposeSignalButton - touchupinside x: %@", x);

        [self.signalDisposable dispose];
    }];
    [self.view addSubview:disposeSignalButton];
    
    UILabel *textLabel = [[UILabel alloc] init];
    textLabel.backgroundColor = [UIColor whiteColor];
    self.textLabel = textLabel;
    [self.view addSubview:textLabel];
    
    UITextField *inputTextField = [UITextField new];
    inputTextField.backgroundColor = [UIColor whiteColor];
    self.inputTextField = inputTextField;
    [self.view addSubview:inputTextField];
    
    RAC(textLabel, text) = inputTextField.rac_textSignal;
    
    [RACObserve(textLabel, text) subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - textLabel.text: %@", x);
    }];
    
    RACTuple *tuple = RACTuplePack(@10, @20);
    NSLog(@"----------> SLReactiveProgrammingDemoController - tuple: %@", tuple);
    RACTupleUnpack(NSNumber *num1, NSNumber *num2) = tuple;
    NSLog(@"----------> SLReactiveProgrammingDemoController - number1: %@, number: %@", num1, num2);
    
    SLRACBlueView *blueView = [[SLRACBlueView alloc] init];
    self.blueView = blueView;
    [[blueView rac_signalForSelector:NSSelectorFromString(@"blueButtonClick:")] subscribeNext:^(RACTuple * _Nullable x) {
        @strongify(self);
        NSLog(@"----------> SLReactiveProgrammingDemoController - blueButtonClick - self.blueView.center: %@", NSStringFromCGPoint(self.blueView.center));
        [self.blueView mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.left.mas_equalTo(20);
            make.right.mas_equalTo(-20);
            make.top.equalTo(self.inputTextField.mas_bottom).mas_offset(100);
            make.height.mas_equalTo(100);
        }];
        [UIView animateWithDuration:0.5 animations:^{
            [self.view layoutIfNeeded];
        }];
        NSLog(@"----------> SLReactiveProgrammingDemoController - blueButtonClick - x: %@", x);
    }];
    [[blueView rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:[NSObject new]] subscribeNext:^(RACTwoTuple<id,NSDictionary *> * _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - blueView.center observer - x: %@", x);
    }];
    [self.view addSubview:blueView];
    
    UIView *redView = [UIView new];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    [redView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(20);
        make.right.mas_equalTo(-20);
        make.top.equalTo(blueView.mas_bottom).mas_offset(20);
        make.height.mas_equalTo(20);
    }];
}

- (void)setupLayout {
    CGFloat margin = 20.0;
    CGFloat buttonHeight = 36.0;
    
    [self.sendSignalButton mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.mas_equalTo(kNavigationBarHeight + margin);
        make.left.mas_equalTo(margin);
        make.height.mas_equalTo(buttonHeight);
    }];
    
    [self.disposeSignalButton mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(self.sendSignalButton.mas_top).mas_offset(0);
        make.left.equalTo(self.sendSignalButton.mas_right).mas_offset(margin);
        make.right.mas_equalTo(-margin);
        make.height.mas_equalTo(buttonHeight);
        make.width.equalTo(self.sendSignalButton.mas_width).mas_offset(0);
    }];
    
    [self.textLabel mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(margin);
        make.right.mas_equalTo(-margin);
        make.top.equalTo(self.sendSignalButton.mas_bottom).mas_offset(margin);
        make.height.mas_equalTo(buttonHeight);
    }];
    
    [self.inputTextField mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(margin);
        make.right.mas_equalTo(-margin);
        make.top.equalTo(self.textLabel.mas_bottom).mas_offset(margin);
        make.height.mas_equalTo(buttonHeight);
    }];
    
    [self.blueView mas_remakeConstraints:^(MASConstraintMaker *make) {
        make.left.mas_equalTo(margin);
        make.right.mas_equalTo(-margin);
        make.top.equalTo(self.inputTextField.mas_bottom).mas_offset(margin);
        make.height.mas_equalTo(150);
    }];
}

- (void)sendSignalButtonClick:(UIButton *)sender {
    // 3. 发送信号
    [self.subscriber sendNext:@"signal"];
    [self.subject sendNext:@"subject"];
    [self.replaySubject sendNext:@"replaySubject3"];
    [self.connectSubscriber sendNext:@"connectSignal"];
    [self.command execute:@"signal3"];
}

- (void)disposeSignalButtonClick:(UIButton *)sender {
    [self.signalDisposable dispose];
}

#pragma mark - private
- (void)testRacSignal {
    
    // RACSignal的使用步骤
    // 1.创建信号 + (RACSignal *)createSignal:(RACDisposable *(^)(id subscriber))didSubscribe;
    // 2.订阅信号，才会激活信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock;
    // 3.发送信号 - (void)sendNext:(id)value
    
    // 1. 创建信号
    RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        // 保存起来，就不会被取消订阅
        self.subscriber = subscriber;
        
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"----------> SLReactiveProgrammingDemoController - testRacSignal - 默认信号发送完毕被取消");
        }];
    }];
    
    // 2. 订阅信号
    // 如果要取消就拿到RACDisposable
    RACDisposable *disposable = [signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - [signal subscribeNext:] - %@", x);
    }];
    self.signalDisposable = disposable;
    
    // 取消订阅
//    [disposable dispose];
}

// 测试RACSubject
- (void)testRacSubject {
    // RACSubject使用步骤
    // 1. 创建信号 [RACSubject subject]，跟RACSignal不一样，创建信号时没有block
    // 2. 订阅信号 - (RACDisposable *)subscribeNext:(void(^)(id x))nextBlock
    // 3. 发送信号 sendNext:(id)value
    
    // 1. 创建信号
    RACSubject *subject = [RACSubject subject];
    self.subject = subject;
    
    // 2. 订阅信号
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - [subject subscribeNext:] - 订阅者1：%@", x);
    }];
    [subject subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - [subject subscribeNext:] - 订阅者2：%@", x);
    }];
}

// 测试RACReplaySubject
- (void)testRACReplaySubject {
    /*
     RACReplaySubject的使用步骤
     1. 创建信号 [RACReplaySubject subject]，跟RACSignal不一样，创建信号时没有block
     2. 可以先订阅信号，也可以先发送信号。
     2.1 订阅信号 - (RACDisposable *)subscribeNext:(void(^)(id x))nextBlock
     2.2 发送信号 sendNext:(id)value
     */
    
    // 1. 创建信号
    RACReplaySubject *replaySubject = [RACReplaySubject subject];
    self.replaySubject = replaySubject;
    
    // 2. 发送信号
    [replaySubject sendNext:@"replaySubject1"];
    [replaySubject sendNext:@"replaySubject2"];
    
    // 订阅信号
    [replaySubject subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - [replaySubject subscribeNext:] - 订阅者1：%@", x);
    }];
    // 如果想一个信号被订阅，就重复播放之前所有值，需要先发送信号，再订阅信号
    [replaySubject subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - [replaySubject subscribeNext:] - 订阅者2：%@", x);
    }];
}

// RACSequence和RACTuple简单实用
- (void)testRACSequenceAndRACTuple {
    NSArray *array = @[@1, @2, @3];
    [array.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACSequenceAndRACTuple - value：%@", x);
    }];
    
    NSDictionary *dict = @{@"name": @"Jack", @"age": @28, @"sex": @"男"};
    [dict.rac_sequence.signal subscribeNext:^(id  _Nullable x) {
        // 捷豹元组，会把元组的值，按顺序给参数里面的变量赋值
        // RACTupleUnpack这是个宏，后面会介绍
        RACTupleUnpack(NSString *key, id value) = x;
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACSequenceAndRACTuple - key：%@, value: %@", key, value);
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACSequenceAndRACTuple - key：%@, value: %@", x[0], x[1]);
        
        /*
         相当于
         NSString *key = x[0];
         id value = x[1];
         */
    }];
}

// RACMulticastConnection的简单使用
- (void)testRACMulticastConnection {
    /*
     RACMulticastConnection：用于当一个信号，被多次订阅时，为了保证创建信号时，避免多次调用创建信号中的block，造成副作用，可以用这个类处理
     使用注意：RACMulticastConnection通过RACSignal的- publish或者- muticast:方法创建
     
     RACMulticastConnection使用步骤
     1. 创建信号 + (RACSignal *)createSignal:(RACDisposable *(^)(id subscriber))didSubscribe
     2. 创建连接 RACMulticastConnection *connect = [signal publish];
     3. 订阅信号，注意：订阅的不再是之前的信号，而是连接的信号。[connect.signal subscribeNext:nextBlock;
     4. 连接 [connect connect];
     5. 发送信号
     */
    
    // 需求：假设在一个信号中发送请求，每次订阅一次都会发送请求，这样就会导致多次请求。
    // 解决：使用RACMulticastConnection就能解决
    
    // 1. 创建信号
    RACSignal *connectSignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACMulticastConnection - [RACSignal createSignal]");
        self.connectSubscriber = subscriber;
        return [RACDisposable disposableWithBlock:^{
            NSLog(@"----------> SLReactiveProgrammingDemoController - testRACMulticastConnection - connectSignal被取消");
        }];
    }];
    
    // 2. 把信号转化为连接类
    RACMulticastConnection *connection = [connectSignal publish];
    
    // 3. 订阅连接类信号
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACMulticastConnection - [connection.signal subscribeNext:] - 订阅者1：%@", x);
    }];
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACMulticastConnection - [connection.signal subscribeNext:] - 订阅者2：%@", x);
    }];
    [connection.signal subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACMulticastConnection - [connection.signal subscribeNext:] - 订阅者3：%@", x);
    }];
    
    // 4. 连接信号
    [connection connect];
}

/*
 RACCommand: RAC中用于处理事件的类，可以把事件如何处理，事件中的数据如何传递，包装到这个类中他可以很方便的监控事件的执行过程
 使用场景：监听按钮点击，网络请求
 */
// RACCommand的简单使用
- (void)testRACCommand {
    /*
     RACCommand的使用步骤
     1. 创建命令 initWithSignalBlock:(RACSignal *(^)(id input))signalBlock
     2. 在signalBlock中，创建RACSignal，并且作为signalBlock的返回值
     3. 执行命令- (RACSignal *)execute:(id)input
     
     RACCommand使用注意
     1. signalBlock必须要返回一个信号，不能传nil
     2. 如果不想要传递信号，直接创建空的信号[RACSignal empty];
     3. RACCommand中信号如果数据传递完，必须调用[subscriber sendCompleted]，这时命令才会执行完毕，否则永远处于执行中。
     
     RACCommand设计思想：内部signalBlock为什么要返回一个信号，这个信号有什么用
     1. 在RAC开发中，通常会把网络请求封装到RACCommand，直接执行某个RACCommand就能发送请求。
     2. 在RACCommand内部请求到数据的时候，需要把请求的数据传递给外界，这时候就需要通过signalBlock返回的信号传递了。
     
     如何拿到RACCommand中返回信号发出的数据
     1. RACCommand有个执行信号源executionSignals，这个是signal of signals(信号的信号)，意思是信号发出的数据是信号，不是普通的类型。
     2. 订阅executionSignals就能拿到RACCommand中返回的信号，然后订阅signalBlock返回的信号，就能获取发出的值。
     */
    
    // 1. 创建命令
    RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id  _Nullable input) {
        // 命令内部传递的参数
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - [[RACCommand alloc] initWithSignalBlock]");
        
        // 2. 返回一个信号，可以返回一个空信号
        return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber>  _Nonnull subscriber) {
            NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - [[RACCommand alloc] initWithSignalBlock] - [RACSignal createSignal]");
            
            // 发送信号
            [subscriber sendNext:@"command"];
            
            // 注意：数据传递完，最好调用sendCompleted，这是命令才执行完毕
            [subscriber sendCompleted];
            
            return [RACDisposable disposableWithBlock:^{
                NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - [[RACCommand alloc] initWithSignalBlock] - [RACSignal createSignal] - 内部信号被取消");
            }];
        }];
    }];
    self.command = command;
    
    // 拿到返回信号方式二
    // command.executionSignals信号中的信号 switchToLatest转化为信号
    [command.executionSignals.switchToLatest subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - 拿到返回信号方式二 - x: %@", x);
    }];
    
    // 拿到返回信号方式一
    RACSignal *signal1 = [command execute:@"signal1"];
    [signal1 subscribeNext:^(id  _Nullable x) {
        NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - 拿到返回信号方式一 - x: %@", x);
    }];
    
    [command execute:@"signal2"];
    
    // 监听命令是否执行完毕
    [command.executing subscribeNext:^(NSNumber * _Nullable x) {
        if ([x boolValue]) {
            NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - [command.executing subscribeNext] - 命令正在执行 - x: %@", x);
        }
        else {
            NSLog(@"----------> SLReactiveProgrammingDemoController - testRACCommand - [command.executing subscribeNext] - 命令完成/没有执行 - x: %@", x);
        }
    }];
}

@end
